package priority_queue

import (
	"channel/cond"
	"channel/queue"
	"context"
	"sort"
	"sync"
)

type PriorityQueue[T any] struct {
	Comparor queue.Comparor[T]
	Queue    []T
	size     int
	notFull  *cond.Cond
	notEmpty *cond.Cond
	mu       *sync.Mutex
}

func (p *PriorityQueue[T]) isFull() bool {
	return p.size == len(p.Queue)
}

func (p *PriorityQueue[T]) isEmpty() bool {
	return len(p.Queue) == 0
}

func (p *PriorityQueue[T]) Enqueue(ctx context.Context, t T) error {
	if ctx.Err() != nil {
		return ctx.Err()
	}
	p.mu.Lock()
	if ctx.Err() != nil {
		return ctx.Err()
	}
	defer p.mu.Unlock()
	for p.isFull() {
		err := p.notFull.WaitV2(ctx)
		if err != nil {
			return err
		}
	}

	p.Queue = append(p.Queue, t)
	sort.Slice(p.Queue, func(i, j int) bool {
		return p.Comparor(p.Queue[i], p.Queue[j])
	})
	if p.isFull() {
		p.notEmpty.Broadcast()
	}
	return nil
}

func (p *PriorityQueue[T]) Dequeue(ctx context.Context) (T, error) {
	if ctx.Err() != nil {
		var t T
		return t, ctx.Err()
	}
	p.mu.Lock()
	if ctx.Err() != nil {
		var t T
		return t, ctx.Err()
	}
	defer p.mu.Unlock()
	for p.isEmpty() {
		err := p.notEmpty.WaitV2(ctx)
		if err != nil {
			var t T
			return t, err
		}
	}
	t := p.Queue[0]
	p.Queue = p.Queue[1:]
	if p.isEmpty() {
		p.notFull.Broadcast()
	}
	return t, nil
}

func NewPriorityQueue[T any](size int, comparor queue.Comparor[T]) *PriorityQueue[T] {
	mu := &sync.Mutex{}
	return &PriorityQueue[T]{
		Comparor: comparor,
		size:     size,
		Queue:    make([]T, 0, size),
		notFull:  cond.NewCond(mu),
		notEmpty: cond.NewCond(mu),
		mu:       mu,
	}
}

var _ queue.Queue[int] = (*PriorityQueue[int])(nil)
